/**
 * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
 *
 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.df.common.utils.http;

import com.google.common.collect.Maps;
import com.df.common.utils.JsonUtil;
import com.df.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;

/** HttpKit */
@Slf4j
public class HttpKit {

  private HttpKit() {}

  /** https 域名校验 */
  private class TrustAnyHostnameVerifier implements HostnameVerifier {
    public boolean verify(String hostname, SSLSession session) {
      return true;
    }
  }

  /** https 证书管理 */
  private class TrustAnyTrustManager implements X509TrustManager {
    public X509Certificate[] getAcceptedIssuers() {
      return null;
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {}

    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {}
  }

  private static final String GET = "GET";
  private static final String POST = "POST";
  private static String CHARSET = "UTF-8";

  private static final SSLSocketFactory sslSocketFactory = initSSLSocketFactory();
  private static final TrustAnyHostnameVerifier trustAnyHostnameVerifier =
      new HttpKit().new TrustAnyHostnameVerifier();

  private static SSLSocketFactory initSSLSocketFactory() {
    try {
      TrustManager[] tm = {new HttpKit().new TrustAnyTrustManager()};
      SSLContext sslContext = SSLContext.getInstance("TLS"); // ("TLS", "SunJSSE");
      sslContext.init(null, tm, new java.security.SecureRandom());
      return sslContext.getSocketFactory();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public static void setCharSet(String charSet) {
    if (StringUtils.isEmpty(charSet)) {
      throw new IllegalArgumentException("charSet can not be blank.");
    }
    HttpKit.CHARSET = charSet;
  }

  private static HttpURLConnection getHttpConnection(
      String url, String method, Map<String, String> headers)
      throws IOException, NoSuchAlgorithmException, NoSuchProviderException,
          KeyManagementException {
    URL _url = new URL(url);
    HttpURLConnection conn = (HttpURLConnection) _url.openConnection();
    if (conn instanceof HttpsURLConnection) {
      ((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
      ((HttpsURLConnection) conn).setHostnameVerifier(trustAnyHostnameVerifier);
    }

    conn.setRequestMethod(method);
    conn.setDoOutput(true);
    conn.setDoInput(true);

    conn.setConnectTimeout(60 * 60 * 1000);
    conn.setReadTimeout(60 * 60 * 1000);

    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    conn.setRequestProperty(
        "User-Agent",
        "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");

    if (headers != null && !headers.isEmpty())
      for (Entry<String, String> entry : headers.entrySet())
        conn.setRequestProperty(entry.getKey(), entry.getValue());

    return conn;
  }

  /** Send GET request */
  public static String get(
      String url, Map<String, String> queryParas, Map<String, String> headers) {
    HttpURLConnection conn = null;
    try {
      conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), GET, headers);
      conn.connect();
      return readResponseString(conn);
    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      if (conn != null) {
        conn.disconnect();
      }
    }
  }

  public static String get(String url, Map<String, String> queryParas) {
    return get(url, queryParas, null);
  }

  public static String get(String url) {
    return get(url, null, null);
  }

  /** Send POST request */
  public static String post(
      String url, Map<String, String> queryParas, String data, Map<String, String> headers) {
    HttpURLConnection conn = null;
    try {
      conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), POST, headers);
      conn.connect();

      OutputStream out = conn.getOutputStream();
      out.write(data != null ? data.getBytes(CHARSET) : null);
      out.flush();
      out.close();

      return readResponseString(conn);
    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      if (conn != null) {
        conn.disconnect();
      }
    }
  }

  public static String post(String url, Map<String, String> queryParas, String data) {
    return post(url, queryParas, data, null);
  }

  public static String post(String url, String data, Map<String, String> headers) {
    return post(url, null, data, headers);
  }

  public static String postJson(String url, Map<String, String> queryParas) {
    Map<String, String> header = Maps.newHashMap();
    header.put("Content-Type", "application/json");
    String jsonBody = JsonUtil.writeValueAsString(queryParas);
    log.info("reqUrl:" + url + ": reqBody:" + jsonBody);
    return post(url, jsonBody, header);
  }

  public static String postJson(String url, Map<String, String> queryParas,Map<String, String> header) {
    header.put("Content-Type", "application/json");
    String jsonBody = JsonUtil.writeValueAsString(queryParas);
    log.info("reqUrl:" + url + ": reqBody:" + jsonBody);
    return post(url, jsonBody, header);
  }

  public static String post(String url, String data) {
    return post(url, null, data, null);
  }

  private static String readResponseString(HttpURLConnection conn) {
    StringBuilder sb = new StringBuilder();
    InputStream inputStream = null;
    try {
      inputStream = conn.getInputStream();
      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, CHARSET));
      String line = null;
      while ((line = reader.readLine()) != null) {
        sb.append(line).append("\n");
      }
      return sb.toString();
    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      if (inputStream != null) {
        try {
          inputStream.close();
        } catch (IOException e) {
          log.error(e.getMessage(), e);
        }
      }
    }
  }

  /** Build queryString of the url */
  private static String buildUrlWithQueryString(String url, Map<String, String> queryParas) {
    if (queryParas == null || queryParas.isEmpty()) return url;

    StringBuilder sb = new StringBuilder(url);
    boolean isFirst;
    if (!url.contains("?")) {
      isFirst = true;
      sb.append("?");
    } else {
      isFirst = false;
    }

    for (Entry<String, String> entry : queryParas.entrySet()) {
      if (isFirst) isFirst = false;
      else sb.append("&");

      String key = entry.getKey();
      String value = entry.getValue();
      if (!StringUtils.isEmpty(value))
        try {
          value = URLEncoder.encode(value, CHARSET);
        } catch (UnsupportedEncodingException e) {
          throw new RuntimeException(e);
        }
      sb.append(key).append("=").append(value);
    }
    return sb.toString();
  }

  public static String readData(HttpServletRequest request) {
    BufferedReader br = null;
    try {
      StringBuilder result = new StringBuilder();
      br = request.getReader();
      for (String line; (line = br.readLine()) != null; ) {
        if (result.length() > 0) {
          result.append("\n");
        }
        result.append(line);
      }

      return result.toString();
    } catch (IOException e) {
      throw new RuntimeException(e);
    } finally {
      if (br != null)
        try {
          br.close();
        } catch (IOException e) {
          log.error(e.getMessage(), e);
        }
    }
  }

  @Deprecated
  public static String readIncommingRequestData(HttpServletRequest request) {
    return readData(request);
  }
}
